/**
 *  Serponix is an arcade game in focus to multiplayer based on the classic game Snake.
 *  Copyright (C) 2010 - 2011  Daniel Vala
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License,
 *  or  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  If you have any question do not hesitate to contact author
 *  on e-mail address: danielvala42@gmail.com
 */
package com.serponix.game;

import com.serponix.game.score.Score;
import com.serponix.abilities.*;
import com.serponix.gui.WindowInterface;
import com.serponix.game.food.Cherry;
import com.serponix.game.food.Cake;
import com.serponix.game.projectiles.Projectile;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.swing.JLabel;
import com.serponix.net.Informace;
import com.serponix.net.Server;

/**
 * Dědí z hlavního modelu hry, reprezentuje Fun mód.
 * @author Daniel Vala
 */
public final class ModelFun extends GameModel {

    private List<Vlastnost> seznamVlastnosti;
    private int vyskytBonusu = 3;
    private int sanceVlastnostiCelkem;

    /**
     * Vytvoří model hry pro mód Fun.
     * @param okno Interface okna pro přístup k ovládání celé hry (např. přístup zpět do menu)
     * @param parametryHry Parametry hry
     * @param skoreStitky Štítky, na které se bude psát skóre všech hráčů.
     * @param panelZprav Panel zpráv, kam se budou psát zprávy o nových událostech.
     */
    public ModelFun(WindowInterface okno, GameParameters parametryHry, GamingArea herniPanel, JLabel[] skoreStitky, MessagePanel panelZprav) {
        super(okno, parametryHry, herniPanel, skoreStitky, panelZprav, parametryHry.getSizeOfGamingArea());
        parametryHry.getServer().setModel(this);
        net = parametryHry.getServer();
        hadi = new Snake[getPocetHadu()];
        vytvorListPocatecnichPozic(10);
        Collections.shuffle(listPocatecnichPozic);
        for (int i = 0; i < hadi.length; i++) {
            hadi[i] = new Snake(this, getHrac(i), listPocatecnichPozic.get(i), -1, Direction.DOWN,
                    3, getVelikostPolicka(), 12, 42, barvy[i], null, true, true);
            getHrac(i).init(this, hadi[i]);
        }
        jidlo = new Cake(this);
        initVlastnosti();

        startHry();
    }

    /**
     * Inicializace všech bonusových vlastností, které mohou být vygenerovány při sebrání bonusu.
     * Každá vlastnost má danou pravděpodobnost vygenerování se.
     */
    private void initVlastnosti() {
        seznamVlastnosti = new ArrayList<Vlastnost>();

        ZasobnikLaseru zasobnikyLaseru = new ZasobnikLaseru(100);
        Rakety rakety = new Rakety(70);
        Jed jed = new Jed(40);
        Tank tank = new Tank(50);
        Steak steak = new Steak(40);
        Tabletka tabletka = new Tabletka(45);
        Pivo pivo = new Pivo(20);
        ElixirZivota elixirZivota = new ElixirZivota(30);
        Stit stit = new Stit(37);
        Kotva kotva = new Kotva(70);
        Lopata lopata = new Lopata(60);
        Duch duch = new Duch(26);
        Swap swap = new Swap(34);
        Vesta vesta = new Vesta(30);
        Pstros pstros = new Pstros(60);
        StrojCasu strojCasu = new StrojCasu(34);

        seznamVlastnosti.add(zasobnikyLaseru);
        seznamVlastnosti.add(rakety);
        seznamVlastnosti.add(jed);
        seznamVlastnosti.add(tank);
        seznamVlastnosti.add(steak);
        seznamVlastnosti.add(tabletka);
        seznamVlastnosti.add(pivo);
        seznamVlastnosti.add(elixirZivota);
        seznamVlastnosti.add(stit);
        seznamVlastnosti.add(kotva);
        seznamVlastnosti.add(lopata);
        seznamVlastnosti.add(duch);
        seznamVlastnosti.add(swap);
        seznamVlastnosti.add(vesta);
        seznamVlastnosti.add(pstros);
        seznamVlastnosti.add(strojCasu);

        // součet všech vlastností celkem
        for (Vlastnost vlastnost : seznamVlastnosti) {
            sanceVlastnostiCelkem += vlastnost.getSance();
        }

        // nastavení pravděpodobnosti každé vlastnosti
        for (Vlastnost vlastnost : seznamVlastnosti) {
            vlastnost.setPravdepodobnost(vlastnost.getSance() / new Double(sanceVlastnostiCelkem));
        }

        // výpis pravděpodobností všech bonusů v procentech
//        for (Vlastnost vlastnost : seznamVlastnosti) {
//            System.out.println("Vlastnost " + vlastnost.getClass() + " sance v procentech je: " + (Math.round(vlastnost.getPravddepodobnost() * 10000) / new Double(100)) + "%");
//        }
    }

    /**
     * Vygeneruje náhodnou vlastnost, přičemž každá 
     * vlastnost má svou různou pravděpodobnost vygenerování.
     * @param had Had, který generuje vlastnost.
     */
    public void vygenerujVlastnost(Snake had) {
        // podařilo se vygenerovat bonus?
        boolean uspech = false;
        int i = 0;
        // generuj vlastnost, dokud se nějakou úspěšně nepodaří vygenerovat
        // nebo počet generování nepřesáhne 1000
        do {
            int nahoda = 1 + generatorNahody.nextInt(sanceVlastnostiCelkem);
            for (Vlastnost vlastnost : seznamVlastnosti) {
                if (nahoda - vlastnost.getSance() < 0) {
                    uspech = vlastnost.proved(this, had);
                    break;
                }
                nahoda -= vlastnost.getSance();
            }
            i++;
            if (i > 1000) {
                getPanelZprav().addMessage("Žádnou vlastnost se nepodařilo vygenerovat.");
                return;
            }
        } while (!uspech);
    }

    /**
     * Zjistí, zda má některý z hadů vlastnost Tron.
     * @return true, pokud je některý z hadů tronem. Jinak false.
     */
    public boolean isTron() {
        for (int i = 0; i < getPocetHadu(); i++) {
            if (hadi[i].isTron()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Posune hada na náhodnou startovní pozici a zkrátí ho o jednu kostičku.
     */
    @Override
    public void resetHada(Snake had) {
        // pokud je had tronem, zabije se
        if (had.isTron()) {
            had.smrt();
            return;
        }
        // had se nastaví na náhodné startovní pozici a zkrátí se o 1 článek
        Collections.shuffle(listPocatecnichPozic);
        had.zkratHada(1);
        had.setHad(listPocatecnichPozic.get(0), -1, Direction.DOWN);
    }

    /**
     * Upozorní model, že je potřeba inicializovat nové kolo hry.
     * Inicializace neproběhne ihned, ale až po skončení kroku hry.
     */
    @Override
    public void newRound() {
        newRound = true;
    }

    /**
     * Inicializuje nové kolo hry. Všechno se nastaví do defaultního stavu pro začátek nového kola.
     * Tzn. všichni hadi začnou na náhodných pozicích směrem dolu po vteřině čekání v základní velikosti a bez žádných vlastností.
     * Vygeneruje se nové jídlo, případně se smaže bonus.
     */
    private void initNewRound() {
        Collections.shuffle(listPocatecnichPozic);
        Snake[] listHadu = getHadi();
        for (int i = 0; i < getPocetHadu(); i++) {
            listHadu[i].setHad(listPocatecnichPozic.get(i), -1, Direction.DOWN);
        }
        odstranVsechnyStrely();
        jidlo.odmapuj(this);
        jidlo.generateFood();
        if (bonus != null) {
            bonus.odmapuj(this);
            bonus = null;
        }
        pockej(1000);
        ozivVsechnyHady();
        newRound = false;
    }

    /**
     * Skore se v tomto modelu nepoužívá.
     * @param skore
     * @param body
     */
    @Override
    public void addScore(Score skore, int body) {
    }

    /**
     * Zjistí, zda jsou všichni hadi naživu.
     * @return True pokud jsou všichni naživu. False, pokud je alespoň jeden had mrtev.
     */
    private boolean isVsichniHadiNazivu() {
        for (int i = 0; i < hadi.length; i++) {
            if (!hadi[i].isAlive()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Pokud je alespoň jeden had ve hře mrtev, s velkou pravděpodobností jej oživí.
     * Pokud je mrtvých hadů více, využije náhody.
     * @param had Had, který oživuje jiného hada.
     * @return True, pokud byl nějáký had oživen. Jinak false.
     */
    public boolean ozivNahodnehoHada(Snake had) {
        if (isVsichniHadiNazivu()) {
            return false;
        }
        for (int i = 0; i < 300; i++) { // 300 pokusů na oživení hada
            int a = generatorNahody.nextInt(hadi.length);
            if (!hadi[a].isAlive()) {
                hadi[a].oziv();
                getPanelZprav().addMessage(String.format("%s oživil %s ", had, hadi[a]));
                return true;
            }
        }
        return false;
    }

    @Override
    public void jidloSezrano(Snake had) {
        had.prodluzHada();
        jidlo.generateFood();
        int stesti = 1 + generatorNahody.nextInt(vyskytBonusu);
        if (stesti == 1 && bonus == null) {
            bonus = new Cherry(this);
        }
    }

    @Override
    public void bonusSezran(Snake had) {
        vygenerujVlastnost(had);
        Toolkit.getDefaultToolkit().beep();
        bonus.odmapuj(this);
        bonus = null;
    }

    /**
     * Reprezentuje tzv. krok celé hry, posune živé hady, aktivní střely, připadně vygeneruje jídlo a bonus.
     * Aktualizuje skore štítky, pošle data klientům a informuje o nutnosti překreslit plátno.
     */
    @Override
    public void krok() {
//        List<Strela> kopieStrel = new ArrayList<Strela>();
//        for (Strela strela : strely) {
//            kopieStrel.add(strela);
//        }

//        Iterator<Strela> it = kopieStrel.iterator();
//        while (it.hasNext()) {
//            it.next().posunSe();
//        }


        // posun všech střel, střely se při pohybu nesmí sami mazat kvůli concurentModificationException
        // pokud byli při pohybu zničeny, metoda posunSe vrátí false. Iterator je poté smaže sám.
        Iterator<Projectile> it = strely.iterator();
        while (it.hasNext()) {
            if (!it.next().posunSe()) {
                it.remove();
            }
        }

//        for (int i = 0; i < strely.size(); i++) {
//            strely.get(i).posunSe();
//        }

        for (int i = 0; i < getPocetHadu(); i++) {
            getHrac(i).posunSe();
            setAllSkoreText(i, getHrac(i).getJmeno() + ":" + hadi[i].score.getPocetVyher() + " ");

            if (hadi[i].isVlastnostNeviditelnost() && getHrac(i).getPacket() != null) {
                posliHada(hadi[i]);
            }
        }

        // pokud během kroku nastali události, jež vyvolali potřebu nového kola, začne se nové kolo
        if (newRound) {
            initNewRound();
        }

        sendAllDataToClients();


        setChanged();
        notifyObservers();
    }

    private void sendAllDataToClients() {
        posliPole();
        posliStavStitku();
        posliInfo();
        posliKrok();
    }

    /**
     * Pošle instrukce pro kreslení všech políček na herní ploše.
     */
    public void posliPole() {
        String zpravaKOdeslani = Informace.POLICKO + Informace.ODDELOVAC1;
        // přidání všech políček do zprávy
        if (jidlo != null) {
            zpravaKOdeslani += jidlo.getInstrukceProKresleni();
        }
        if (bonus != null) {
            zpravaKOdeslani += bonus.getInstrukceProKresleni();
        }
        for (int j = 0; j < getPocetHadu(); j++) {
            zpravaKOdeslani += hadi[j].getInstrukceProKresleni(false);
        }

        for (int i = 0; i < strely.size(); i++) {
            zpravaKOdeslani += strely.get(i).getInstrukceProKresleni();
        }

        net.poslatData(zpravaKOdeslani);
    }

    /**
     * Pošle informaci, že má klient uskutečnit další krok hry.
     */
    public void posliKrok() {
        String zpravaKOdeslani = Informace.KROK + Informace.ODDELOVAC1;
        net.poslatData(zpravaKOdeslani);
    }

    public void posliStavStitku() {
        String zpravaKOdeslani = Informace.SKORE + Informace.ODDELOVAC1;

        // přidání textů všech štítků do zprávy
        for (int i = 0; i < getPocetHadu(); i++) {
            zpravaKOdeslani += getSkoreText(i) + Informace.ODDELOVAC3;
        }
        net.poslatData(zpravaKOdeslani);
    }

    /**
     * Pošle nové zprávy do panelu zpráv klientům.
     */
    public void posliInfo() {
        String zpravaKOdeslani = getPanelZprav().getMessageToSend();
        if (zpravaKOdeslani != null) {
            net.poslatData(zpravaKOdeslani);
        }
    }

    /**
     * Pošle informace pro kreslení daného hada klientovi, který ho vlastní.
     * @param had Had, kterému se budou posílat instrukce pro kreslení jeho hada.
     */
    public void posliHada(Snake had) {
        String zpravaKOdeslani = Informace.POLICKO + Informace.ODDELOVAC1;
        zpravaKOdeslani += had.getInstrukceProKresleni(true);
        ((Server) net).posliDataKlientovi(zpravaKOdeslani, had.getHrac().getPacket());
    }

    /**
     * Vykreslí mapu, hady a jídlo.
     * @param g
     */
    public void paint(Graphics g) {
        if (!zobrazVeciNaPlose) {

            for (Snake had : hadi) {
                had.draw(g);
            }
            for (int i = 0; i < strely.size(); i++) {
                strely.get(i).draw(g);
            }
//            for (Projectile strela : strely) {
//                strela.draw(g);
//            } // TODO concurentModificationException
            jidlo.draw(g);
            if (bonus != null) {
                bonus.draw(g);
            }
        } else {
            zobrazVeciNaPlose(g);
        }
    }

    /**
     * Vykreslí zdi mapy.
     * @param g Grafický objekt pro kreslení.
     */
    public void paintZdi(Graphics g) {
        if (!zobrazVeciNaPlose) {
            getMapa().kresliMapu(g);
        }
    }
}
